-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FIX TreeDropdownField fails validation if it is empty #1578
FIX TreeDropdownField fails validation if it is empty #1578
Conversation
5b38bc5
to
071c57f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't quite seem there, I used the following setup:
composer require silverstripe/linkfield
<?php
namespace {
use SilverStripe\Forms\RequiredFields;
use SilverStripe\LinkField\Form\LinkField;
use SilverStripe\LinkField\ORM\DBLink;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\CMS\Model\SiteTree;
class Page extends SiteTree
{
private static array $db = [
'DbLink' => DBLink::class
];
private static $has_one = [
'HasOneLink' => Link::class,
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldsToTab(
'Root.Main',
[
LinkField::create('HasOneLink'),
LinkField::create('DbLink'),
]
);
return $fields;
}
public function getCMSValidator()
{
return RequiredFields::create(
'DbLink',
'HasOneLink'
);
}
}
}
I tested this issue on simple TreeDropdownField. And yes, if input value is "0" TreeDropdownField passes validation. <?php
use CWP\CWP\PageTypes\BasePage;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\Forms\TreeDropdownField;
class Page extends BasePage
{
private static $has_one = [
'MyPage' => SiteTree::class,
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Main', TreeDropdownField::create('MyPage', 'MyPage', SiteTree::class));
return $fields;
}
public function getCMSValidator()
{
return RequiredFields::create(
'MyPage',
);
}
} |
The original issue is about TreeDropdownField in react forms i.e. the form inside the popup modal used in silverstripe/linkfield. The code sample you've provided is for a TreeDropdown on the regular page edit form. I'm not sure it makes a difference or not to the validation if the TreeDropdownfield is in a react form or not. Based on the video I made when testing (linked above) it seems like it's not working the way it should with this PR? |
I couldn't reproduce this solution with Screen.Recording.2023-09-13.at.10.42.33.AM.movAfter: Screen.Recording.2023-09-13.at.10.43.32.AM.mov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is there a Treedropdownfield for "Insert an external link" in your video? That modal is supposed to look like this:
I retested on my local for inserting a link to a "Page on this site", and filling in the required link text - it still lets me insert (Search or choose Page) for "Select a page" - which is the same as the existing behaviour on 4.13
It looks like validation for form in app/src/Page.php <?php
namespace {
use SilverStripe\Forms\RequiredFields;
use SilverStripe\LinkField\Form\LinkField;
use SilverStripe\LinkField\ORM\DBLink;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Forms\CompositeValidator;
class Page extends SiteTree
{
private static array $db = [
'DbLink' => DBLink::class
];
private static $has_one = [
'HasOneLink' => Link::class,
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldsToTab(
'Root.Main',
[
LinkField::create('HasOneLink'),
LinkField::create('DbLink'),
]
);
return $fields;
}
// This method validates only Page Form
public function getCMSCompositeValidator(): CompositeValidator
{
$validator = parent::getCMSCompositeValidator();
return $validator->addValidator(RequiredFields::create([
'DbLink',
]));
}
}
} app/src/Extensions/SiteTreeLinkExtension.php <?php
namespace App\Extensions;
use SilverStripe\Forms\CompositeValidator;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\ORM\DataExtension;
class SiteTreeLinkExtension extends DataExtension
{
public function updateCMSCompositeValidator(CompositeValidator $compositeValidator): void
{
$compositeValidator->addValidator(RequiredFields::create([
'PageID',
]));
}
} app/_config/extensions.yml SilverStripe\LinkField\Models\SiteTreeLink:
extensions:
- App\Extensions\SiteTreeLinkExtension And also you should implement vendor/silverstripe/linkfield/src/Form/FormFactory.php // This method validates Link Form
protected function getValidator($controller, $name, $context)
{
if (!$context['LinkType'] instanceof DataObject) {
return null;
}
$compositeValidator = $context['LinkType']->getCMSCompositeValidator();
return $compositeValidator;
} You should see the following result. Screen.Recording.2023-09-14.at.8.36.18.PM.mov |
I shouldn't need to add a method to a linkfield class (FormFactory.php) to test this issue :-) Seems like there's an issue with linkfield PHP validation not working? Could you please raise a new issue on the silverstripe/linkfield repo - I did some brief testing and it looks the the legacy I think the original issue is more about client side JS validation that's not quite working correctly? In my original test where I linked to a video it seems that this wasn't working correctly, and there hasn't been any code updates since then? |
2862842
to
071c57f
Compare
@emteknetnz, I don't quite understand your expectations regarding the behavior of this validation. |
I'm just confused. My core confusion is that I don't even know how to replicate the issue in the description. I don't know what I'm supposed to test. I've looked at the issue description multiple times and I cannot replicate the issue.
So I don't know what I'm supposed to test here |
The easiest way to make sure that the validation of a given field does not work is to simply add a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fundamentally the wrong approach - we shouldn't be looking to change the value that TreeDropdownField
returns, as people might be relying on it in their own validation.
I think the problem is actually just that RequiredFields
doesn't know how to treat the string "0"
that is returned. This is easy to check by adding a TreeDropdownField
directly to a page or other data object and mark it required, like so:
private static $has_one = [
'TestValidationPage' => SiteTree::class,
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldsToTab('Root.Main', [
TreeDropdownField::create('TestValidationPageID', null, SiteTree::class),
]);
return $fields;
}
public function getCMSCompositeValidator(): CompositeValidator
{
$validator = parent::getCMSCompositeValidator();
$validator->addValidator(RequiredFields::create([
'TestValidationPageID',
]));
return $validator;
}
A validation error is never produced for this, which makes it clear the validation logic is wrong. While this could be solved by changing the value TreeDropdownField
uses to represent "empty", doing so would be a breaking change as I mentioned at the start of this comment. Instead, we should make this change to RequiredFields
:
$value = isset($data[$fieldName]) ? $data[$fieldName] : null;
+ // TreeDropdownFields give a value of '0' when no item is selected.
+ if ($formField instanceof TreeDropdownField && $value === '0') {
+ $error = true;
+ } elseif (is_array($value)) {
- if (is_array($value)) {
Note that I'm not testing this with LinkField yet because that's a lot of extra complexity. Lets get the base case working first, then circle back to LinkField. |
The original issue description also references |
I don't think that validation should be so specified for each field. Also how explained here ThreeDropdownField by default has value = '', so I don't think why we should change default value to '0' when we clean this field., we should be constant. |
Also I don't think that this solution help us, since we need check field before we send it to the server. This is the main issue. |
If we need to have it specified for a specific field type to avoid breaking changes, then we should do so. In this case I believe that is necessary. But lets start with the base case (i.e. just a
Right now validation doesn't work for the base case. After we've solved that, I'll be happy to talk more about the LinkField scenario. |
How about case where '0' will be valid value for TreeDropdownField? User can populate with ["Yes" => 1, "No" => 0]. |
"0" is already explicitly not a valid value for this form field, because the single empty value has been hardcoded to 0. Changing that would, as I've mentioned, be a breaking change for anyone who is already explicitly checking for that in their own custom validation. We cannot change that in a minor (let alone a patch) release. |
I think we should additionally discuss TreedropdownField validation. |
My 2c here would be not to have field type specific stuff in required fields validator, it shouldn't know about how each field validates itself. |
This is less about the field validating itself, and more about checking if the field has a value set. I agree that having |
Could we make Could be a bit unconventional approach, but won't be BC breaking. |
That would be fine - though I don't know if we actually want to have a |
I think there might be a valid usecase for the empty value being a value that is not null or empty, e.g. with custom fields where people might already have custom validations.. but happy to not have that API and only have that extra handling within the RequiredFields without an actual public or documented API. It probably wouldn't be the first place. But saying that it nearly equals to having that tight coupling with one specific field type 🤷. |
Yeah good point about other field types. I think at the end of the day I'd be happy with either approach. |
Probably a more robust option here is to just work out what the Dataobject class is by going |
Closing in favor of PRs on #1616 |
Parent issue
Test steps:
app/src/Page.php
app/src/Extensions/SiteTreeLinkExtension.php
_app/src/Extensions/MyFormFactory.php
app/_config/extensions.yml
Result
Screen.Recording.2023-09-14.at.8.36.18.PM.mov